import os
import re
import sys
import subprocess
import shlex
import json
import time
import logging
import tarfile
import ipaddress
from threading import Thread
from .expection import *
from .config import *
from concurrent.futures import ThreadPoolExecutor, wait, ALL_COMPLETED

try:
    import yaml
except ImportError:
    print('Your system does\'t have python3 yaml library, starting to install yaml by pip3...')
    cmd = 'pip3 install pyyaml'
    p = subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
    _, stderr = p.communicate()
    if p.returncode:
        print(stderr.decode('utf-8'))
        print('Failed to install pyyaml, please run command to install yaml: {}'.format(cmd))
    else:
        print('Successfully installed pyyaml, please rerun command for {}!'.format(TOOL_NAME))
    sys.exit(p.returncode)


def logging_config(args):
    subdir = time.strftime('%Y-%m-%d_%H-%M-%S', time.localtime())
    if args.list_hardware:
        logdir = os.path.join(LOG_BASE_DIR, 'list_hardware_%s' % subdir)
    elif args.list_testcase:
        logdir = os.path.join(LOG_BASE_DIR, 'list_testcase_%s' % subdir)
    else:
        logdir = os.path.join(LOG_BASE_DIR, '%s_%s' % (args.category, subdir))
    os.makedirs(os.path.join(logdir, 'cases'))
    os.makedirs(os.path.join(logdir, 'hostinfo'))
    logging.basicConfig(level=logging.DEBUG, filemode='w', filename='%s/ancert.log'
                        % logdir, format='%(asctime)s %(threadName)s %(levelname)s %(message)s')
    return logdir


def collect_host_info(logdir):
    os.system('/bin/bash utils/collect_host_info.sh {0} > {0}/host_info.log 2>&1'
              .format(os.path.join(logdir, 'hostinfo')))


def run_task(gname, tasks):
    data = list()
    alarm = type('alarm', (object,), {'stop': False})
    for t in tasks:
        data.append((t.name, t.timeout))
    th = Thread(target=status_bar, args=(alarm, max([d[1] for d in data]), gname.title(), data))
    to_run_futures = []
    with ThreadPoolExecutor(max_workers=POOL_MAX_WORKERS) as pool:
        for task in tasks:
            logging.debug('starting to run task %s on %s' % (task.name, task.test.controller.name.strip()))
            future = pool.submit(task.run)
            to_run_futures.append(future)
        th.start()
        while True:
            done, not_done = wait(to_run_futures, timeout=300, return_when=ALL_COMPLETED)
            if not not_done:
                logging.info('Finished %d tests.' % len(done))
                break
            logging.info('Waiting rest %d tasks' % (len(not_done)))
        for future in to_run_futures:
            if future.exception() is not None:
                logging.error('Future hit exception, result: %s, exception: %s'
                              % (str(future.result()), str(future.exception())))
    alarm.stop = True
    th.join()


def run_local_cmd(cmd, exit_msg='', shell=False, logoutput=True, loginit=True):
    if shell:
        p = subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
    else:
        p = subprocess.Popen(shlex.split(cmd), stdout=subprocess.PIPE, stderr=subprocess.PIPE)
    stdout, stderr = p.communicate()
    rc = p.returncode
    if loginit:
        logging.debug('CMD: %s' % cmd)
        if logoutput:
            if stdout.decode('utf-8'):
                logging.debug('%s' % stdout.decode('utf-8'))
            if stderr.decode('utf-8'):
                logging.debug('%s' % stderr.decode('utf-8'))
    if rc:
        if exit_msg:
            if loginit:
                logging.error('failed to run cmd: %s', cmd)
                logging.info(exit_msg)
            else:
                print(exit_msg)
            raise RunCommandError(stderr.decode('utf-8'))
        if stderr.decode('utf-8'):
            return rc, stderr.decode('utf-8')
    return rc, stdout.decode('utf-8')


def regex_match_cmd_output(cmd, pattern, tool='search'):
    rc, output = run_local_cmd(cmd, 'Failed to run cmd: %s' % cmd)
    if rc:
        return False, ''
    if tool == 'search':
        m = pattern.search(output)
    elif tool == 'match':
        m = pattern.match(output)
    if m:
        return True, m.group()
    return False, ''


def fixed_length(string, length=8):
    return '0'*(length - len(string)) + string


def is_sub_class_code(class_code, ctr_class_code):
    for code in class_code:
        if ctr_class_code.startswith(code):
            return True
    return False


def is_virtual_platform():
    cmd = 'virt-what'
    rc, output = run_local_cmd(cmd, 'Failed to run cmd: %s' % cmd)
    logging.debug('current platform is %s' % output)
    if output.strip():
        return True
    return False


def load_tests_yaml(folder):
    cases = {}
    case_meta = yaml.safe_load(open(os.path.join(folder, 'case.yaml'), 'r'))
    for d in os.listdir(folder):
        if not os.path.isdir(os.path.join(folder, d)):
            continue
        if d == 'lib':
            continue
        cases[d] = {}
        for test_d in os.listdir(os.path.join(folder, d)):
            if not os.path.isdir(os.path.join(folder, d, test_d)):
                continue
            if test_d == 'lib':
                continue
            yaml_path = os.path.join(folder, d, test_d, '{}.yaml'.format(test_d))
            if not os.path.exists(yaml_path):
                print('Dose not has yaml file %s for %s' % (yaml_path, d))
                continue
            cases[d][test_d] = {}
            for key, val in yaml.safe_load(open(yaml_path, 'r')).items():
                if key in ['testgroup', 'category']:
                    cases[d][test_d].update({key: val})
                if key in ['cases']:
                    cases[d][test_d]['cases'] = []
                    for c in val:
                        tmp = {}
                        tmp.update(case_meta)
                        if c['filepath'].endswith('.sh'):
                            c['filetype'] = 'shell'
                        elif c['filepath'].endswith('.py'):
                            c['filetype'] = 'python'
                        else:
                            raise ConfigError('Dose not support this type file %s' % c['filepath'])
                        tmp.update(c)
                        cases[d][test_d]['cases'].append(tmp)
    return cases


def status_bar(alarm, max_runtime, prefix, data, sleep_time=0.1):
    def blinking(x, j, max_runtime):
        if j >= max_runtime:
            return '....Done 100%'
        v, p = x % 8, int(j*100/max_runtime)
        bstr = ''
        if v == 0:
            bstr = '       -'
        elif v == 1:
            bstr = '.      /'
        elif v == 2:
            bstr = '..     |'
        elif v == 3:
            bstr = '...    \\'
        elif v == 4:
            bstr = '....   -'
        elif v == 5:
            bstr = '.....  /'
        elif v == 6:
            bstr = '...... |'
        else:
            bstr = '.......\\'
        bstr += ' %s%%' % p
        return bstr

    def deploy(j, x):
        msg_lst = []
        for i, (key, ti) in enumerate(data):
            msg = ' %s:%s' % (key, blinking(j + i, x, ti))
            msg = '\r\x1b[K' + prefix + msg
            msg_lst.append(msg)
            if msg_lst:
                for m in msg_lst:
                    time.sleep(0.2)
                    sys.stdout.write(m)
                    sys.stdout.flush()
            else:
                sys.stdout.write(msg)
                sys.stdout.flush()

    for i in range(int((max_runtime+2)/sleep_time)):
        time.sleep(sleep_time)
        if alarm.stop:
            deploy(i, max_runtime+2)
            break
        deploy(i, int(i * sleep_time))
    sys.stdout.write('\r\x1b[K')
    sys.stdout.flush()


def is_network_interface_linkup(interface):
    cmd = 'ethtool %s' % interface
    pattern = re.compile('Link detected: yes')
    rc, output = regex_match_cmd_output(cmd, pattern)
    return rc


def is_pvs_disk(name, disks):
    for d in disks:
        if d.startswith(name):
            return True
    return False


def map_partition_to_drive():
    partition2drive = {}
    cmd = 'ls -l /dev/disk/by-path'
    rc, output = run_local_cmd(cmd, exit_msg='Failed to get disk by-path info!', shell=True)
    if rc or not output:
        return {}
    tmp = {}
    for line in output.split('\n'):
        if '->' not in line:
            continue
        line = line.strip()
        info = line.split(' ')
        if info[-2].strip() not in ['->']:
            continue
        tmp[info[-3]] = os.path.realpath('/dev/disk/by-path/%s' % info[-1])
    pci_path = list(tmp.keys())
    pci_path.sort()
    while pci_path:
        key1 = pci_path.pop(0)
        partition2drive[tmp[key1]] = tmp[key1]
        while pci_path:
            key2 = pci_path.pop(0)
            if key2.startswith(key1):
                partition2drive[tmp[key2]] = tmp[key1]
            else:
                pci_path.insert(0, key2)
                break
    return partition2drive


def get_lspci_vmms(pci_bdf):
    info = {}
    cmd = 'lspci -vmms %s' %  pci_bdf
    rc, output = run_local_cmd(cmd, shell=True)
    if not rc and output:
        for line in output.split('\n'):
            line = line.strip()
            if not line:
                continue
            tmp = line.split(':', 1)
            if len(tmp) == 2:
                key, val = tmp
                info.update({key.strip(): val.strip()})
    return info

def get_device_name_from_hwdb(dev):
    logging.debug('try to get device name from hwdb or lspci for syspath %s' % dev.get_property('SYSPATH'))
    device_name = ''
    if dev.get_property('SYSPATH'):
        cmd = 'udevadm test-builtin hwdb %s' % dev.get_property('SYSPATH')
        rc, output = run_local_cmd(cmd, shell=True)
        if not rc and output:
            id_vendor_name, id_model_name = '', ''
            for line in output.split('\n'):
                line = line.strip()
                if not line:
                    continue
                if 'ID_VENDOR_FROM_DATABASE' in line:
                    id_vendor_name = line.split('=', 1)[1]
                if 'ID_MODEL_FROM_DATABASE' in line:
                    id_model_name = line.split('=', 1)[1]
            device_name = (id_vendor_name + ' ' + id_model_name).strip()
    if not device_name:
        logging.debug('can not get device name(syspath %s) from hwdb, try lspci' % dev.get_property('SYSPATH'))
        if dev.get_property('PCI_SLOT_NAME'):
            info = get_lspci_vmms(dev.get_property('PCI_SLOT_NAME'))
            if 'Vendor' in info:
                device_name += ' ' + info['Vendor']
            if 'Device' in info:
                device_name += ' ' + info['Device']
            if 'SDevice' in info:
                if info['SDevice']:
                    device_name += ' ' + '(%s)' % info['SDevice']
    return device_name.strip()

def get_boot_disk(partition2drive):
    cmd = 'df -P / | tail -n 1 | awk \'/.*/ {print $1}\''
    rc, output = run_local_cmd(cmd, exit_msg='Failed to get boot disk!', shell=True)
    if rc or not output:
        return ''
    if '/dev/mapper' in output:
        cmd = 'df -P /boot | tail -n 1 | awk \'/.*/ {print $1}\''
        rc, output = run_local_cmd(cmd, exit_msg='Failed to get boot disk!', shell=True)
        if rc or not output:
            return ''
    disk = output.strip()
    return partition2drive[disk] if disk in partition2drive else disk


def get_mount_point_free_space(mnt_point):
    cmd = 'df -P %s | tail -n 1 | awk \'{print $4}\'' % mnt_point
    rc, output = run_local_cmd(cmd, exit_msg='Failed to get %s free space!' % mnt_point, shell=True)
    if rc or not output:
        return 0
    free_size_kb = output.strip()
    if free_size_kb.isdigit():
        return int(int(free_size_kb)/1024/1024)


def get_mount_point(dev_path):
    cmd = 'lsblk -idno MOUNTPOINT -p %s' % dev_path
    rc, output = run_local_cmd(cmd, exit_msg='Failed to get %s mount point!' % dev_path, shell=True)
    if rc or not output:
        return ''
    if os.path.exists(output.strip()):
        return output.strip()
    logging.debug('mount point %s does not exist!' % dev_path)
    return ''


def get_pvs_disk(partition2drive):
    pvs_disk = []
    cmd = 'pvs -o pv_name --reportformat json'
    rc, output = run_local_cmd(cmd, exit_msg='Failed get pvs disk!', shell=True)
    if rc:
        return pvs_disk
    for pv in [item['pv_name'] for item in json.loads(output)['report'][0]['pv']]:
        if pv in partition2drive:
            pvs_disk.append(partition2drive[pv])
        else:
            pvs_disk.append(pv)
    return list(set(pvs_disk))


def get_architecture():
    cmd = 'uname -p'
    rc, output = run_local_cmd(cmd, exit_msg='Failed to get architecture!', shell=True)
    if 'x86' in output.lower():
        return 'x86'
    elif 'aarch' in output.lower():
        return 'arm'
    elif 'loongarch' in output.lower():
        return 'loongarch'
    raise RunCommandError('Does not support this architecture')


def get_kernel_version():
    cmd = 'uname -r'
    rc, output = run_local_cmd(cmd, exit_msg='Failed to get kernel version!', shell=True)
    print('Kernel Version: %s' % output.strip())


def get_os_version():
    cmd = 'cat /etc/os-release'
    rc, output = run_local_cmd(cmd, exit_msg='Failed to get OS version!', shell=True)
    print('OS Relase: %s' % re.search('PRETTY_NAME.*', output.strip()).group().split('"')[-2])


def has_mount_partition(devname):
    cmd = 'mount | grep "^%s"' % devname
    rc, output = run_local_cmd(cmd, shell=True)
    if rc:
        return False
    return True


def get_disk_size(devpath):
    cmd = 'lsblk --output SIZE -b -n -d %s' % devpath
    rc, output = run_local_cmd(cmd, exit_msg='Failed to get disk %s size!' % devpath, shell=True)
    if rc:
        return 0
    size_byte = int(output.strip())
    return round(size_byte / 1024 / 1024 / 1024, 2)


def get_all_disks():
    all_disks = {}
    cmd = 'lsblk -indo NAME'
    rc, output = run_local_cmd(cmd, exit_msg='Failed to disk info', shell=True)
    if not output:
        raise ConfigError('Failed to get any disk on the system, please double check!')
    for disk in output.split('\n'):
        if not disk.strip():
            continue
        dev_path = '/dev/%s' % disk.strip()
        if os.path.exists(dev_path):
            all_disks.update({dev_path: [None, None, []]}) # ctr instance, disk instance, [disk partition]
    if not all_disks:
        raise ConfigError('Failed to get any available disk on the system, please double check!')
    return all_disks


def get_system_info():
    cmd = 'dmidecode -t system'
    rc, output = run_local_cmd(cmd, exit_msg='Failed to get system information!', shell=True)
    if rc:
        return ''
    name = ''
    manufacturer_pattern = re.compile('Manufacturer: (.*)')
    product_pattern = re.compile('Product Name: (.*)')
    for line in output.split('\n\t'):
        m = manufacturer_pattern.match(line)
        if m:
            name += m.group(1) + ' '
        m = product_pattern.match(line)
        if m:
            name += m.group(1) + ' '
    return name


def get_memory_info():
    info = []
    cmd = 'dmidecode --type memory'
    rc, output = run_local_cmd(cmd, exit_msg='Failed to get memory information!', shell=True)
    if rc:
        return info
    pattern_list = [re.compile('Manufacturer: (.*)'), re.compile('Part Number: (.*)'),
                    re.compile('Type: (.*)'), re.compile('Speed: (.*)'),
                    re.compile('Size: (.*)')]
    memory_info = []
    for slot in output.split('Memory Device\n\t'):
        if 'dmidecode' in slot or 'No Module Installed' in slot:
            continue
        info = []
        for line in slot.split('\n\t'):
            for index, pattern in enumerate(pattern_list):
                m = pattern.match(line.strip())
                if m:
                    info.append(m.group(1))
                    break
        memory_info.append(['%s %s %s %s' % (info[3], info[4], info[1], info[2].replace(' ', '')), \
                           info[0].replace(' ', '')])
    return memory_info


def get_bios_vendor():
    vendor = ''
    cmd = 'dmidecode --type bios'
    info = dump_dmidecode(cmd, 'bios', 'BIOS Information\n\t')
    if info:
        vendor += info[0].get('Vendor', '')
        vendor += ' '
        if info[0].get('Version'):
            vendor += '[%s]' % info[0].get('Version', '').strip()
    return vendor.strip()


def dump_dmidecode(cmd, ty, spt):
    info = []
    _, output = run_local_cmd(cmd, exit_msg='Failed to dump %s information!' % ty, shell=True)
    output = output.replace('\n\t\t', '|')
    for outx in output.split('\n\n'):
        if spt not in outx:
            continue
        outy = outx.split(spt)
        if len(outy) != 2:
            logging.warning('%s split (%s) is not currect:\n%s' % (cmd, spt, output))
            continue
        item = {}
        for line in outx.split(spt)[1].split('\n\t'):
            key, val = line.split(':', 1)
            item.update({key: val})
        info.append(item)
    return info


def save_device_info(comp_insts, dstree, args, logdir):
    def dump_os_relase():
        info = {}
        cmd = 'cat /etc/os-release'
        _, output = run_local_cmd(cmd, exit_msg='Failed to dump os version!', shell=True)
        for line in output.split('\n'):
            if not line.strip() or '=' not in line:
                continue
            key, val = line.strip().split('=', 1)
            info.update({key: val})
        return info

    def dump_kernel_version():
        cmd = 'uname -a'
        _, output = run_local_cmd(cmd, exit_msg='Failed to dump kernel version!', shell=True)
        return {'uname': output.strip()}

    def dump_kernel_module_info():
        cmd = 'lsmod | awk \'{print $1}\''
        _, output = run_local_cmd(cmd, exit_msg='Failed to dump kernel version!', shell=True)
        modules = []
        for mod in output.split('\n'):
            mod = mod.strip()
            if mod == 'Module' or not mod:
                continue
            modules.append(mod)
        driver_info = {}
        for drv in dstree.drivers:
            drv = drv.strip()
            if drv in modules:
                _, output = run_local_cmd('modinfo %s' % drv, shell=True)
                info = {drv: {}}
                for line in output.split('\n'):
                    if ':' not in line:
                        continue
                    key, val = line.split(':', 1)
                    val = val.strip()
                    info[drv].update({key: val})
                driver_info.update(info)
        return driver_info

    all_info = {}
    for inst in comp_insts:
        for key, val in inst.dump_device_info().items():
            if key not in all_info:
                all_info[key] = val
                continue
            all_info[key].extend(val)

    all_info['DMI'] = {}
    cmd = 'dmidecode --type processor'
    all_info['DMI']['processor'] = dump_dmidecode(cmd, 'processor', 'Processor Information\n\t')

    cmd = 'dmidecode --type bios'
    all_info['DMI']['bios'] = dump_dmidecode(cmd, 'bios', 'BIOS Information\n\t')

    cmd = 'dmidecode --type system'
    all_info['DMI']['system'] = dump_dmidecode(cmd, 'system', 'System Information\n\t')

    cmd = 'dmidecode --type memory'
    all_info['DMI']['memory'] = dump_dmidecode(cmd, 'memory', 'Memory Device\n\t')

    cmd = 'dmidecode --type baseboard'
    all_info['DMI']['baseboard'] = dump_dmidecode(cmd, 'baseboard', 'Base Board Information\n\t')

    cmd = 'dmidecode --type slot'
    all_info['DMI']['slot'] = dump_dmidecode(cmd, 'slot', 'Slot Information\n\t')

    cmd = 'dmidecode --type connector'
    all_info['DMI']['connector'] = dump_dmidecode(cmd, 'connector', 'Connector Information\n\t')

    cmd = 'dmidecode --type chassis'
    all_info['DMI']['chassis'] = dump_dmidecode(cmd, 'chassis', 'Chassis Information\n\t')

    all_info['release'] = dump_os_relase()
    all_info['kernel'] = dump_kernel_version()
    all_info['driver'] = dump_kernel_module_info()

    all_info['boot_disk'] = ''
    for inst in comp_insts:
        if not hasattr(inst, 'boot_disk'):
            continue
        for ctr in inst.available:
            for dk in ctr.devices:
                if dk.__class__.__name__ == 'DevDisk' and dk.is_boot_disk:
                    all_info['boot_disk'] = get_boot_disk_hw_info(dk, ctr, dstree)
                    break
            else:
                continue
        else:
            logging.warning('failed to get boot disk info!')

    all_info['test_info'] = {}
    all_info['test_info']['option'] = vars(args)

    info_file = os.path.join(logdir, ALL_DEVICE_INFO)
    with open(info_file, 'w') as fd:
        fd.write(json.dumps(all_info, indent=4))


def save_test_result(comp_insts, args, logdir, result):
    info, detail = {}, {}
    info['test_info'] = {}
    info['test_info']['all_test_result'] = 'PASS' if result else 'FAIL'
    info['test_info']['detail'] = detail
    logging.info('total test result: %s' % info['test_info']['all_test_result'])
    for cpt in comp_insts:
        if cpt.name not in detail:
            detail[cpt.name] = []
        for gname, tasks in cpt.next_tasks():
            for t in tasks:
                t_info = {'case': t.name,
                          'result': 'PASS' if t.result() == 0 else 'FAIL',
                          'driver': t.test.controller.driver,
                          'module': t.test.controller.module,
                          'ctr_name': t.test.controller.name.strip(),
                          'ctr_bdf': t.test.controller.bdf,
                          'ctr_syspath': t.test.controller.syspath}
                if hasattr(cpt, 'boot_disk'): # sub class of Storage
                    for dk in t.test.controller.available_devices:
                        if dk.get_property('STORAGE_TESTED_DISK') == 'yes':
                            if 'tested_disk' not in t_info:
                                t_info['tested_disk'] = []
                            model, serial = dk.get_property('ID_MODEL').replace('_', ' '), dk.get_property('ID_SERIAL_SHORT')
                            id_serial = dk.get_property('ID_SERIAL')
                            id_serial_short = dk.get_property('ID_SERIAL_SHORT')
                            if id_serial and id_serial_short:
                                model = id_serial.replace(id_serial_short, '').replace('_', ' ').strip()
                                serial = dk.get_property('ID_SERIAL_SHORT')
                            t_info['tested_disk'].append({'devname': dk.dev_path,
                                                          'size': dk.get_property('DISK_SIZE'),
                                                          'revision': dk.get_property('ID_REVISION'),
                                                          'model': model,
                                                          'serial': serial,
                                                          'vendor': dk.get_property('DISK_VENDOR'),
                                                          'driver': dk.get_property('DISK_DRIVER'),
                                                          'transport': dk.get_property('DISK_TRANSPORT'),
                                                          'disk_type': dk.get_property('DISK_TYPE'),
                                                          'name': dk.get_property('DRIVE_FULL_NAME')})
                detail[cpt.name].append(t_info)
    all_info = {}
    info_file = os.path.join(logdir, ALL_DEVICE_INFO)
    with open(info_file, 'r') as fd:
        all_info = json.loads(fd.read())
    if not all_info or 'test_info' not in all_info:
        logging.error('failed to load info file %s!' % info_file)
        print('Failed to save log to %s' % info_file)
        return False
    all_info['test_info'].update(info)
    with open(info_file, 'w') as fd:
        fd.write(json.dumps(all_info, indent=4))
    return True

def get_int_value_from_file(filepath):
    cmd = 'cat %s' % filepath
    _, ret = run_local_cmd(cmd, exit_msg='Failed to get %s value' % filepath, shell=True)
    ret = ret.strip()
    if not ret.isdigit():
        logging.error('Value %s is not a number from %s' % (ret, filepath))
        raise RunCommandError('Value type error')
    return int(ret)


def get_disk_type(dev_path):
    devname = dev_path.split('/')[-1]
    rotational = '/sys/block/%s/queue/rotational' % devname
    if os.path.exists(rotational):
        val = get_int_value_from_file(rotational)
        if int(val) == 0:
            return 'SSD'
        elif int(val) == 1:
            return 'HDD'
        else:
            logging.warning('Get unexpect value %s from %s' % (val, rotational))
    return ''


def get_disk_vendor(sys_path):
    vendor_path = '/sys/%s/device/vendor' % sys_path
    cmd = 'cat %s' % vendor_path
    if os.path.exists(vendor_path):
        rc, ret = run_local_cmd(cmd, shell=True)
        if rc:
            logging.warning('failed to get %s vendor, error %s' % (sys_path, ret))
            return ''
        return ret.strip()
    return ''


def get_bmc_vendor(comp):
    vendor, manu_id, prod_id = '', '', ''
    if comp.is_virtual_platform:
        return vendor
    cmd = 'bmc-info'
    if os.path.exists('/dev/ipmi0') or os.path.exists('/dev/ipmi/0') or os.path.exists('/dev/ipmidev/0'):
        rc, ret = run_local_cmd(cmd, shell=True)
        if not rc:
            for item in ret.split('\n'):
                if 'Manufacturer ID' in item:
                    manu_id = item.split(':')[-1].strip()
                if 'Product ID' in item:
                    prod_id = item.split(':')[-1].strip()
        vendor = manu_id
        if prod_id:
            vendor += ' [%s]' % prod_id
    else:
        logging.debug('ipmi interface is unavailable, maybe ipmi service is stopped.')
    return vendor


def get_disk_transport(dev_path):
    cmd = 'lsblk -idno TRAN %s' % dev_path
    rc, ret = run_local_cmd(cmd, shell=True)
    if rc:
        logging.warning('failed to get %s transport, error: %s' % (dev_path, ret))
        return ''
    return ret.strip()


def get_disk_driver(dev_path):
    devname = dev_path.split('/')[-1]
    block_path = '/sys/block/%s/device/driver' % devname
    if not os.path.exists(block_path):
        return ''
    driver_path = os.path.realpath(block_path)
    logging.debug('%s driver path %s' % (dev_path, driver_path))
    if driver_path:
        return driver_path.split('/')[-1]
    return ''


def get_boot_disk_hw_info(dk, ctr, dstree):
    disk_name = ''
    for dev in dstree.devices:
        if not dk.dev_path.startswith(dev.get_property('DEVNAME')):
            continue
        if dev.get_property('DEVTYPE') != 'disk' or dev.get_property('DEVTYPE') == 'partition':
            continue
        if ctr.is_raid:
            logging.warning('This is a raid controller')
            return disk_name
        if dev.get_property('ID_SERIAL'):
            disk_name += dev.get_property('ID_SERIAL').replace('_', ' ')
        disk_type = get_disk_type(dev.get_property('DEVNAME'))
        if disk_type:
            disk_name += ' %s' % disk_type
        devname = dev.get_property('DEVNAME').split('/')[-1]
        logical_block_size = '/sys/block/%s/queue/logical_block_size' % devname
        size = '/sys/block/%s/size' % devname
        if os.path.exists(logical_block_size) and os.path.exists(size):
            sector_size = get_int_value_from_file(logical_block_size)
            total_size = get_int_value_from_file(size)
            size_gb = int(total_size*sector_size/1000/1000/1000)
            disk_name += ' %sGB' % size_gb
    return disk_name


def get_module_name(driver_name):
    driver_path = '/sys/bus/pci/drivers/%s/module' % driver_name
    if not os.path.exists(driver_path):
        return ''
    module_path = os.path.realpath(driver_path)
    logging.debug('%s driver module path is %s' % (driver_path, module_path))
    if module_path:
        module_name = module_path.split('/')[-1]
        _, modules = run_local_cmd('lsmod', shell=True)
        if module_name in modules:
            return module_name
        else:
            logging.debug('does not find %s in output of lsmod' % module_name)
            return ''
    return ''


def pack_log_dir(logdir, args, result):
    if not os.path.exists(logdir):
        print('Error: There are no log files to package in path {}'.format(logdir))
        return False
    logdir_dirname = os.path.dirname(logdir)
    abs_log_tar_name = logdir_dirname + '/%s.tar' % os.path.basename(logdir)
    try:
        with tarfile.open(abs_log_tar_name, "w") as tar:
            tar.add(logdir, arcname=os.path.basename(logdir))
    except Exception as e:
        print(e)
        print('Error: log pack failed, log tar path: %s.' % logdir )
        return False

    # generate the MD5 verification file
    log_tar_name = os.path.basename(abs_log_tar_name)
    md5_path = '%s/checksum' % logdir_dirname
    md5_fname = '%s.md5' % log_tar_name
    rpm_verify_f = 'rpm_verify.txt'
    if not os.path.exists(md5_path):
        os.makedirs(md5_path)
    os.chdir(logdir_dirname)
    cmd = 'md5sum {log_tar_name} > {checksum}/{md5_fname} && '.format(log_tar_name=log_tar_name,
                                    checksum=os.path.basename(md5_path), md5_fname=md5_fname)
    if rpm_verify(md5_path, rpm_verify_f):
        cmd += 'tar -rf {log_tar_name} {checksum}/{rpm_verify} && '.format(log_tar_name=log_tar_name,
                                                                checksum=os.path.basename(md5_path),
                                                                rpm_verify=rpm_verify_f)
    cmd += """
        tar -rf {log_tar_name} {checksum}/{md5_fname} && 
        tar -tf {log_tar_name} | grep tar.md5 && 
        rm -rf {md5_path} 
        """.format(log_tar_name=log_tar_name, md5_fname=md5_fname,
                   checksum=os.path.basename(md5_path), md5_path=md5_path)
    try:
        if os.path.exists(abs_log_tar_name):
            run_local_cmd(cmd, exit_msg='Failed to gen md5 %s' % md5_fname, shell=True)
    except Exception as e:
        print('Failed to gen md5 %s' % md5_fname)
        print('Error: {}'.format(e))
        return False

    if args.category:
        print('%s Test *%s*!' % (args.category, 'PASS' if result else 'FAIL'))
        print('Save tar log file to: %s' % abs_log_tar_name)
        print('Please send tar log file %s OpenAnolis hardware compatibility team. '
           % ('to' if result else 'and ask help from'))

    if args.list_hardware:
        print('Save tar log file to: %s' % abs_log_tar_name)
    return True


def rpm_verify(path, name):
    cmd_rpm = 'rpm -qa | grep %s' % TOOL_NAME
    try:
        rc, output = run_local_cmd(cmd_rpm, exit_msg='There is no matching package %s.'
                                   % TOOL_NAME, shell=True)
        if output and rc == 0:
            cmd_rpm_verify = 'rpm -V %s |tee %s/%s' % (TOOL_NAME, path, name)
            _rc, _ = run_local_cmd(cmd_rpm_verify, exit_msg='Failed to verify %s rpm.'
                                 % TOOL_NAME,shell=True)
            if _rc == 0:
                return True
        return False
    except Exception as e:
        print(e)
        return False


def check_ipaddr_connection(ipaddr):
    try:
        ipaddress.ip_address(ipaddr)
    except ValueError as e:
        print('%s ip address format is not correct!' % ipaddr)
        return False
    try:
        run_local_cmd('ping -c 1 %s' % ipaddr,
                       exit_msg='Failed to ping ip address %s' % ipaddr, shell=True, loginit=False)
    except RunCommandError as e:
        print(e)
        return False
    return True

def check_interface(interface):
    try:
        run_local_cmd('ifconfig %s' % interface,
                       exit_msg='%s is not exist' % interface, shell=True, loginit=False)
    except RunCommandError as e:
        print(e)
        return False
    return True

def generate_summary_report(args, tasks, logdir):
    message_format = '{:<28s}{:<12s}{:<12s}{:<68s}{:>s}\n'
    report_path = os.path.join(logdir, 'summary.report')
    with open(report_path, 'w+') as fd:
        fd.write(message_format.format('TestCase', 'Category', 'Result', 'Log', 'Message'))
        for t in tasks:
            if t.result() == 0:
                status = 'PASS'
            elif t.result() == 2:
                status = 'SKIP'
            else:
                status = 'FAIL'
            fd.write(message_format.format(t.test.case.name, t.test.case.component,
                                           status, t.log_relative, t.get_exit_msg()))
    print('Summary Report: %s' % report_path)
    if args.tone:
        print('\n')
        print('Detail result:')
        print('-'*26)
        with open(report_path, 'r') as fd:
            print(fd.read())
        print('\nResult for Tone:')
        print('-'*26)
        for t in tasks:
            if t.result() == 0:
                print('%s TEST PASS!' % t.test.case.name)
            elif t.result() == 2:
                print('%s TEST SKIP!' % t.test.case.name)
            else:
                print('%s TEST FAIL!' % t.test.case.name)
        print('')